(function (window) {
    /**
     * @msg: [模拟Vue]
     */
    class Vm {
        /**
         * @msg: [构造函数]
         * @param {object} config [配置对象]
         * @return {object}: [实例化对象]
         */
        constructor(config) {
            //配置对象默认化处理
            config = {
                data: {},
                ...config
            };

            //节点容器
            this.$el = document.querySelector(config.el); //拿取dom节点
            //未传入节点选择器
            if (!this.$el) throw new Error('config\'s property of el is required, or $el must be a document node'); //抛出错误

            Object.keys(config.data).forEach(key => {
                // this[key] = config.data[key];
                //对data进行属性劫持
                Object.defineProperty(this, key, {
                    //getter函数
                    get() {
                        return config.data[key];
                    },
                    // setter函数
                    set(newValue) {
                        //属性修改
                        config.data[key] = newValue;
                        //进行局部视图更新
                        this.updateElement(key);
                    },
                    enumerable: true, //属性可枚举
                    configurable: true, //属性描述符可配置
                })
            })

            this.directives(); //创建指令集

            this.compileNodes(); //编译虚拟dom

            this.createElement(); //创建真实dom

        }

        /**
         * @msg: [编译虚拟dom]
         */
        compileNodes() {
            this._vnode = this.compilechildNodes(this.$el);
        }
        /**
         * @msg: [递归编译虚拟dom]
         */
        compilechildNodes(node) {
            const children = []; //子虚拟dom集合

            //遍历nodeList
            [...node.childNodes].forEach(v => {
                //过滤空文本节点
                if (v.nodeType === 3 && !v.nodeValue.trim()) return;

                //映射生成虚拟dom
                const _node = {
                    nodeType: v.nodeType,
                    nodeValue: v.nodeValue || '',
                    nodeName: v.nodeName,
                    children: [],
                    data: [], //data依赖
                    attrs: {}, //属性节点树
                };

                //映射出属性树
                if (v.attributes) {
                    [...v.attributes].forEach(attr => {
                        _node.attrs[attr.name] = attr.value.trim();
                    });
                }

                //正则
                const reg = /\{\{([^{}]+)\}\}/g;
                //属性集
                const props = _node.nodeValue.match(reg) || []
                //映射进入data
                _node.data = props.map(v => v.replace(reg, '$1').trim());

                children.push(_node); //虚拟dom进入集合列表

                if (v.childNodes.length > 0) { //当前节点存在子节点、递归遍历当前节点的childNodes
                    _node.children = this.compilechildNodes(v);
                }
            });

            return children;
        }

        /**
         * @msg: [创建真实dom]
         */
        createElement() {
            //清空原始内容
            this.$el.innerHTML = '';
            const frag = document.createDocumentFragment(); //创建文档片段
            this.createElementChildren(frag, this._vnode); //递归创建真实dom
            this.$el.appendChild(frag); //文档片段追加进dom树
        }
        /**
         * @msg: [递归创建真实dom，并实现插值]
         */
        createElementChildren(parentNode, nodeList) {

            nodeList.forEach(v => {
                let node;
                switch (v.nodeType) { //根据不同的节点类型创建不同的节点
                    case 1: //元素节点
                        node = document.createElement(v.nodeName);
                        break;
                    case 3: //文本节点
                        const value = this.replaceElementText(v.nodeValue) //插值替换
                        node = document.createTextNode(value);
                        break;
                    default:
                        return;
                };

                //保留节点引用
                v.node = node;

                //原有属性保留
                Object.keys(v.attrs).forEach(key => {
                    //以v-开头 即指令
                    if (key.startsWith('v-')) {
                        const name = key.substring(2); //指令名
                        this.$directives[name] && this.$directives[name](v.node, v.attrs[key]);
                    } else {
                        node.setAttribute(key, v.attrs[key]);
                    }

                });

                //节点追加进文档片段
                parentNode.appendChild(node);
                //递归
                this.createElementChildren(node, v.children);
            });
        }

        /**
         * @msg: [模板插值替换]
         */
        replaceElementText(value) {
            //定义正则 提取{{}}内容
            const reg = /\{\{([^{}]+)\}\}/g;
            //提取属性集
            const props = value.match(reg);

            Array.isArray(props) && props.forEach(v => { //遍历属性集
                const prop = v.replace(reg, '$1').trim(); //获取属性名字段
                //未在data声明属性错误抛出
                if (!this.hasOwnProperty(prop)) throw new Error(`Property or method '${prop}' is not defined on the instance`);
                value = value.replace(v, this[prop]); //进行内容替换
            });

            return value; //返回替换后的value
        }

        //更新队列
        _updateQueue = []
        //定时器
        _timer = null
        /**
         * @msg: [根据虚拟dom局部更新视图]
         * @param {string} key 当前变更的属性
         */
        updateElement(key) {
            //进入队列
            this._updateQueue = [...new Set([key, ...this._updateQueue])];
            //节流处理
            if (!this._timer) {
                this._timer = setTimeout(() => {
                    //更新队列进行遍历更新
                    while (this._updateQueue.length) {
                        const key = this._updateQueue.pop();
                        this.updateElementChild(this._vnode, key);
                    }
                    //重置定时器
                    this._timer = null;
                }, 50);
            }
        }
        /**
         * @msg: [递归进行视图更新]
         * @param {array} nodeList 虚拟节点列表
         * @param {string} key 当前变更的属性
         */
        updateElementChild(nodeList, key) {
            //遍历虚拟dom列表
            nodeList.forEach(v => {
                //如果节点存在data依赖，nodeValue进行更新
                if (v.data.includes(key)) {
                    v.node.nodeValue = this.replaceElementText(v.nodeValue);
                }

                //如果指令存在data依赖
                Object.keys(v.attrs).forEach(attr => {
                    if (attr.indexOf('v-') === 0 && v.attrs[attr] === key) { //指令属性
                        const name = attr.substring(2); //指令名
                        this.$directives[name] && this.$directives[name](v.node, v.attrs[attr]); //重新执行指令集
                    }
                })

                //递归自身，遍历所有子节点
                this.updateElementChild(v.children, key);
            })
        }


        /**
         * @msg: [指令集]
         */
        directives() {
            this.$directives = {
                for () {},
                if: (el, value) => {
                    el._parent = el.parentNode; //父元素引用
                },
                show: (el, value) => {
                    switch (value) {
                        case 'true':
                            value = true;
                            break;
                        case 'false':
                            value = false;
                            break;
                        default:
                            value = this[value];
                            break;
                    }
                    el.style.display = value ? 'block' : 'none';
                },
                text: (el, value) => {
                    el.innerText = this[value];
                },
                html: (el, value) => {
                    el.innerHTML = this[value];
                },
                model: (el, value) => {
                    el.value = this[value];
                    el.addEventListener('input', (event) => {
                        this[value] = event.target.value;
                    }, false);
                }
            }
        }

    }


































    window.Vm = Vm;
})(window);